﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Mvc;
using KnockoutSignalR.Bindings;

namespace KnockoutSignalR
{
    namespace Bindings
    {
        public abstract class BindingBase
        {
            public abstract string BindingType { get; }
            public string BindingValue { get; protected set; }

            public override string ToString()
            {
                return string.Format("{0}: {1}", this.BindingType, this.BindingValue);
            }
        }

        public class TextBinding : BindingBase
        {
            protected TextBinding(){}
            public static TextBinding Create<TModel, TResult>(TModel model, Expression<Func<TModel, TResult>> expression)
            {
                return new TextBinding {BindingValue = KnockoutBindings.GetPropertyInfo(model, expression).Name};
            }
            public static TextBinding Create(string value)
            {
                return new TextBinding { BindingValue = value };
            }

            public override string BindingType
            {
                get { return "text"; }
            }
        }

        public class ClickBinding : BindingBase
        {

            private ClickBinding() { }
            public static ClickBinding Create<TModel>(TModel model, Expression<Action<TModel>> expression)
            {
                return new ClickBinding { BindingValue = KnockoutBindings.GetMethodInfo(model, expression).Name };
            }
            public static ClickBinding Create(string value)
            {
                return new ClickBinding { BindingValue = value };
            }

            public override string BindingType
            {
                get { return "click"; }
            }
        }
        public class ValueBinding : TextBinding
        {
            public static ValueBinding Create<TModel, TResult>(TModel model, Expression<Func<TModel, TResult>> expression)
            {
                return new ValueBinding { BindingValue = KnockoutBindings.GetPropertyInfo(model, expression).Name };
            }
            public static ValueBinding Create(string value)
            {
                return new ValueBinding { BindingValue = value };
            }
            public override string BindingType
            {
                get { return "value"; }
            }
        }
        public class RawBinding<TModel> : IBinding<TModel>
        {
            public RawBinding(TModel model, string text)
            {
                Model = model;
                Text = text;
            }

            public TModel Model { get; set; }
            public string Text { get; set; }

            public override string ToString()
            {
                return Text;
            }
        }

        public interface IBinding<TModel>
        {
            TModel Model { get; set; }
        }
    }

    public class BindingHelper<TModel> : IBinding<TModel>
    {
        public TModel Model { get; set; }
    }

    public static class KnockoutBindings
    {
        public static BindingBase Text<TModel, TResult>(this BindingHelper<TModel> helper, Expression<Func<TModel, TResult>> expression)
        {
            return TextBinding.Create(helper.Model, expression);
        }

        public static BindingBase Text<TModel>(this BindingHelper<TModel> helper, string value)
        {
            return TextBinding.Create(value);
        }

        public static BindingBase Value<TModel, TResult>(this BindingHelper<TModel> helper, Expression<Func<TModel, TResult>> expression)
        {
            return ValueBinding.Create(helper.Model, expression);
        }

        public static ClickBinding Click<TModel>(this BindingHelper<TModel> helper, Expression<Action<TModel>> expression)
        {
            return ClickBinding.Create(helper.Model, expression);
        }

        public static RawBinding<TModel> Raw<TModel>(this BindingHelper<TModel> helper, string text)
        {
            return new RawBinding<TModel>(helper.Model, text);
        }

        public static IHtmlString DataBind<TModel>(this HtmlHelper<TModel> html, params Func<BindingHelper<TModel>,IBinding<TModel>>[] bindingsFuncs)
        {
            var bindingHelper = new BindingHelper<TModel> {Model = html.ViewData.Model};
            var bindings = bindingsFuncs.Select(bf => bf(bindingHelper)).ToList();
            foreach (var binding in bindings)
            {
                binding.Model = html.ViewData.Model;
            }
            return new MvcHtmlString(
                string.Format(@"data-bind=""{0}""",
                              string.Join(", ", bindings.Select(binding => binding.ToString()))));
        }

        public static PropertyInfo GetPropertyInfo<TSource, TProperty>(TSource source, Expression<Func<TSource, TProperty>> propertyLambda)
        {
            Type type = typeof(TSource);

            MemberExpression member = propertyLambda.Body as MemberExpression;
            if (member == null)
                throw new ArgumentException(string.Format(
                    "Expression '{0}' refers to a method, not a property.",
                    propertyLambda));

            PropertyInfo propInfo = member.Member as PropertyInfo;
            if (propInfo == null)
                throw new ArgumentException(string.Format(
                    "Expression '{0}' refers to a field, not a property.",
                    propertyLambda));

            if (type != propInfo.ReflectedType &&
                !type.IsSubclassOf(propInfo.ReflectedType))
                throw new ArgumentException(string.Format(
                    "Expresion '{0}' refers to a property that is not from type {1}.",
                    propertyLambda,
                    type));

            return propInfo;
        }

        public static MethodInfo GetMethodInfo<TSource>(TSource source, Expression<Action<TSource>> methodLambda)
        {
            Type type = typeof(TSource);

            var methodExpression = methodLambda.Body as MethodCallExpression;
            if (methodExpression == null)
                throw new ArgumentException(string.Format(
                    "Expression '{0}' does not refer to a method",
                    methodLambda));

            var methodInfo = methodExpression.Method;

            if (type != methodInfo.ReflectedType &&
                !type.IsSubclassOf(methodInfo.ReflectedType))
                throw new ArgumentException(string.Format(
                    "Expresion '{0}' refers to a method that is not from type {1}.",
                    methodLambda,
                    type));

            return methodInfo;
        }
    }
}
